using System;
using NUnit.Framework;

namespace AutoTest.Core.Utils
{
    /// <summary>
    /// Verify a class correctly implements the IEquatable contract.
    /// http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx
    /// </summary>
    public class EquatableAssert<T> where T : class, IEquatable<T>
    {
        /// <param name="same1">should be different instances than same2</param>
        /// <param name="same2">should be different instances than same1</param>
        /// <param name="different"></param>
        public void AssertImplementsIEquatable(T same1, T same2, T different)
        {
            AssertIdenticalInstancesAreEqual(same1);
            AssertSameInstancesAreEqual(same1, same2);
            AssertEqualityIsSymmetric(same1, same2);
            AssertEqualityIsSymmetric(same1, different);
            AssertEqualityIsTransitive(same1, different);
            AssertSuccessiveCallsToXEqualsYReturnsTheSameValue(same1, different);
            AssertSuccessiveCallsToXEqualsYReturnsTheSameValue(same1, same1);
            AssertShouldNotEqualNull(same1);
            AssertShouldNotEqualNull(same2);
            AssertShouldNotEqualNull(different);
        }

        private void AssertIdenticalInstancesAreEqual(T x)
        {
            Assert.IsTrue(x.Equals(x));
            Assert.IsTrue(x.Equals((object) x));
        }

        private void AssertSameInstancesAreEqual(T x, T y)
        {
            Assert.IsTrue(x.Equals(y));
            Assert.IsTrue(x.Equals((object) y));
        }

        private void AssertEqualityIsSymmetric(T x, T y)
        {
            Assert.AreEqual(x.Equals(y), y.Equals(x));
            Assert.AreEqual(x.Equals((object) y), y.Equals((object) x));
        }

        private void AssertEqualityIsTransitive(T same, T different)
        {
            for (int xIndex = 0; xIndex <= 1; xIndex++)
            {
                for (int yIndex = 0; yIndex < 1; yIndex++)
                {
                    for (int zIndex = 0; zIndex < 1; zIndex++)
                    {
                        var x = xIndex == 0 ? same : different;
                        var y = yIndex == 0 ? same : different;
                        var z = zIndex == 0 ? same : different;
                        Assert.AreEqual(x.Equals(z), (x.Equals(y) && y.Equals(z)));
                        Assert.AreEqual(x.Equals((object) z), (x.Equals((object) y) && y.Equals((object) z)));
                    }
                }
            }
        }

        private void AssertSuccessiveCallsToXEqualsYReturnsTheSameValue(T x, T y)
        {
            bool result = x.Equals(y);
            Assert.AreEqual(result, x.Equals(y));
            Assert.AreEqual(result, x.Equals(y));
            Assert.AreEqual(result, x.Equals(y));
            Assert.AreEqual(result, x.Equals((object) y));
            Assert.AreEqual(result, x.Equals((object) y));
            Assert.AreEqual(result, x.Equals((object) y));
        }

        private void AssertShouldNotEqualNull(T x)
        {
            Assert.IsFalse(x.Equals((object) null));
            Assert.IsFalse(x.Equals(null));
        }
    }
}